#include <math.h>

#include <sqbind/sqbBindMacros.h>
#include <sqbind/sqbClassDefinition.h>

typedef uint32_t TypeID;

// class to be bound with auto release
//
class Object
{
public:
  friend class Factory;

  TypeID GetTypeID() const
  {
    return m_typeID;
  }

  virtual void AddReference()
  {
    ++m_referenceCount;
  }
  virtual void Release()
  {
    --m_referenceCount;
    if (m_referenceCount == 0)
    {
      delete this;
    }
  }

protected:
  Object(TypeID typeID) : m_typeID(typeID), m_referenceCount(0) { }
  virtual ~Object() { }

private:
  const TypeID  m_typeID;
  uint32_t      m_referenceCount;

  Object(const Object &rhs);
  const Object &operator = (const Object &rhs);
};

// make sure the release hook is always bound and the object can never be copied
//
SQBIND_DECLARE_AUTO_RELEASE_CLASS(Object);

// custom release hook that ensures release is called appropriately
//
static SQInteger ObjectReleaseHook(SQUserPointer ptr, SQInteger)
{
  Object *instance = static_cast<Object *>(ptr);
  instance->Release();
  return 0;
}

// factory class that makes objects
//
class Factory
{
public:
  static Factory* GetInstance()
  {
    return &m_instance;
  }

  Object* Create(TypeID typeID)
  {
    Object* instance = new Object(typeID);
    instance->AddReference();
    return instance;
  }

private:
  static Factory m_instance;

  Factory() { }
  Factory(const Factory &rhs);
  const Factory &operator = (const Factory &rhs);
};

Factory Factory::m_instance;

// Factory is a singleton so make it non copyable.
//
SQBIND_DECLARE_NON_COPYABLE_CLASS(Factory);

// this will bind the above class to the root table of vm
//
void BindAutoReleaseClass(HSQUIRRELVM vm)
{
  sq_pushroottable(vm);

  // register the Factory class that can make IUnknowns.
  //
  sqb::NoConstructorClassDefinition<Factory>(vm, -1, _SC("Factory"))
    .SingletonFunction(Factory::GetInstance(), &Factory::Create, _SC("Create"));

  // bind the custom release hook for the Object class.
  //
  sqb::ClassTypeTag<Object>::Get()->SetReleaseHook(&ObjectReleaseHook);

  // register the Object class.
  //
  sqb::NoConstructorClassDefinition<Object>(vm, -1, _SC("IUnknown"))
    .ClassFunction(&Object::GetTypeID, _SC("GetTypeID"));

  sq_poptop(vm);
}
